﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using snat.algorithms.network.TFIDF;
using System.Threading.Tasks;
using snat.algorithms.node.LinkPrediction;
using System.IO;

namespace snat.algorithms {
    /// <summary>
    /// The message clusterer finds all groups of similar messages.
    /// Used for forwarding prediction.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class MessageClusterer<T> : IAlgorithm where T : Document {

        /*
            USE

            NetworkModel.Instance.Connect("mysql5.dcs.warwick.ac.uk", 3389, "snag", "snag_read", "JeBngXFmMQFMheBD");

            NetworkModel.Instance.InitNodes(new RelabelClusterFactory("../../../snat/Resources/emails"));
            NetworkModel.Instance.Init();
            MessageClusterer<EnronDocument> mc = new MessageClusterer<EnronDocument>(0.35);
            mc.Register();
            NetworkModel.Instance.Play();
        */

        double threshold;
        Dictionary<Document, List<Document>> clusters;

        public static Dictionary<String, Term> Terms;

        List<int> peopleOfInterest;

        public void limitPeople(List<int> l) {
            peopleOfInterest = l;
        }

        List<List<Document>> forwardChains;
        int minChainLength;

        public List<Document> AllDocs() {
            List<Document> l = new List<Document>();
            foreach (Document d in clusters.Keys) {
                l.Add(d);
                foreach (Document d2 in clusters[d]) {
                    l.Add(d2);
                }
            }
            return l;
        }

        // constructor, takes in a similarity threshold t.
        public MessageClusterer(double t) {
            threshold = t;
            clusters = new Dictionary<Document, List<Document>>();
            Terms = new Dictionary<string, Term>();
            forwardChains = new List<List<Document>>();
            minChainLength = 3;
            peopleOfInterest = new List<int>();
        }

        public void OnMessageProcessed(Message m) {

            if (peopleOfInterest.Count > 0) {
                if (!peopleOfInterest.Contains(m.Sender.ID)) { return; }
                bool cont = false;
                foreach (LinkPredictorNode<EnronDocument> n in m.Recipients) {
                    if (peopleOfInterest.Contains(n.ID)) {
                        cont = true;
                        break;
                    }
                }
                if (!cont) { return; }
            }

            // create a message Document, and populate TFIDF information.
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, true });

            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)clusters.Count / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(AllDocs(),
                d => { d.PopulateTFIDF(termsDict); });
            document.PopulateTFIDF(termsDict);

            // find most similar cluster head, thats above a threshold.
            double mostsimilar = 0;
            Document mostsimilarDoc = null;
            foreach (Document d in clusters.Keys) {
                double cs = d.CosineSimilarity(document);
                if (cs > threshold) {
                    if (cs > mostsimilar) {
                        mostsimilar = cs;
                        mostsimilarDoc = d;
                    }
                }
            }

            // add to cluster or form a new cluster.
            if (mostsimilarDoc != null) {
                clusters[mostsimilarDoc].Add(document);
            } else {
                clusters.Add(document, new List<Document>());
            }
        }

        public void OnTapeEnded() {
            // work out how many clusters there are.

            Console.ReadLine();

            for (int i = 1; i < 20; i++) {
                int count = 0;
                foreach (Document d in clusters.Keys) {
                    if (clusters[d].Count + 1 == i) {
                        count++;
                    }
                }
                Console.WriteLine("There are {0} clusters with a size of {1}", count, i);
            }

            Console.ReadLine();

            StreamWriter output = new StreamWriter("MessageClusterer_output.txt");

            using (output) {
                output.WriteLine("There are {0} clusters of documents", clusters.Count);

                Console.WriteLine("There are {0} clusters of documents", clusters.Count);

                List<Document> denseOrder = new List<Document>();

                foreach (Document d in clusters.Keys.OrderByDescending(e => clusters[e].Count)) {
                    int temp = clusters[d].Count;
                    denseOrder.Add(d);
                }

                output.WriteLine("The cluster with the largest density has {0} documents, and the cluster head is : {1}", clusters[denseOrder.First()].Count + 1, denseOrder.First().ToString());
                Console.WriteLine("The cluster with the largest density has {0} documents, and the cluster head is : {1}", clusters[denseOrder.First()].Count + 1, denseOrder.First().ToString());

                foreach (Document d in denseOrder) {
                    if (clusters[d].Count > 1) {
                        output.WriteLine("This cluster has {0} docs, and is clustered around {1}", clusters[d].Count + 1, d.ToString());
                        Console.WriteLine("This cluster has {0} docs, and is clustered around {1}", clusters[d].Count + 1, d.ToString());
                    }
                }

                // perform analysis on the clusters.
                foreach (Document d in clusters.Keys) {
                    if (clusters[d].Count + 1 > 1) {
                        List<Document> docs = new List<Document>();
                        docs.Add(d);
                        foreach (Document d2 in clusters[d]) {
                            docs.Add(d2);
                        }

                        docs.OrderBy(e => e.Date);

                        foreach (Document d2 in docs) {
                            List<Document> docs2 = new List<Document>();
                            foreach (Document d3 in docs) {
                                if (d3.Date > d2.Date) {
                                    docs2.Add(d3);
                                }
                            }
                            List<List<Document>> sequences = generateSequence(d2, docs2);

                            if (sequences == null || sequences.Count == 0) {
                                output.WriteLine("THERE WERE NO FORWARD CHAINS FOR THIS CLUSTER");
                                Console.WriteLine("THERE WERE NO FORWARD CHAINS FOR THIS CLUSTER");
                            } else {
                                foreach (List<Document> seq in sequences) {
                                    if (seq.Count >= minChainLength) {
                                        forwardChains.Add(seq);
                                    }
                                }
                            }
                        }
                    }

                    output.WriteLine("FINISHED PROCESSING FORWARD CHAINS FOR CLUSTER : {0}", d.ToString());
                    Console.WriteLine("FINISHED PROCESSING FORWARD CHAINS FOR CLUSTER : {0}", d.ToString());
                }
                
                output.WriteLine();
                output.WriteLine();

                Console.ReadLine();

                for (int i = 1; i < 7; i++) {
                    int count = 0;
                    foreach (List<Document> seq in forwardChains) {
                        if (seq.Count + 1 == i) {
                            count++;
                        }
                    }
                    Console.WriteLine("There are {0} forward chains with a size of {1}", count, i);
                }

                Console.ReadLine();

                output.WriteLine("FORWARD CHAINS ------------------------------------------------------");

                foreach (List<Document> seq in forwardChains) {
                    output.WriteLine("NEW FORWARD CHAIN. LENGTH : {0}", seq.Count);
                    foreach (Document d in seq) {
                        output.WriteLine("Document. Date : {0}. SenderID : {1}. Recipients : ", d.Date, d.Sender.ID);
                        foreach (BaseNode b in d.Recipients) {
                            output.Write("{0} , ", b.ID);
                        }
                        output.WriteLine("Message is:");
                        output.WriteLine(d.ToString());
                    }
                    output.WriteLine();
                }
            }

        }

        public List<List<Document>> generateSequence(Document d, List<Document> docs) {

            List<List<Document>> sequences = new List<List<Document>>();

            foreach (ClusterNode n in d.Recipients) {
                if (n != null) {
                    foreach (Document d2 in docs) {
                        if (d2.Sender.ID == n.ID) {
                            if ((DateTime.Compare(d.Date, d2.Date) < 0) && (DateTime.Compare(d.Date.AddDays(7.0), d2.Date) > 0)) {
                                List<Document> newSeq = new List<Document>();
                                newSeq.Add(d);
                                newSeq.Add(d2);

                                List<Document> docs2 = new List<Document>();
                                foreach (Document d3 in docs) {
                                    if (d3.Date > d.Date) {
                                        docs2.Add(d3);
                                    }
                                }

                                List<List<Document>> newSequences = new List<List<Document>>();
                                newSequences = continueSequence(newSeq, docs2);

                                // do something with list of sequences for this particular document

                                if (newSequences == null || newSequences.Count == 0) {
                                    sequences.Add(newSeq);
                                } else {
                                    foreach (List<Document> sequence in newSequences) {
                                        sequences.Add(sequence);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (sequences.Count > 0) {
                return sequences;
            } else {
                return null;
            }
        }

        public List<List<Document>> continueSequence(List<Document> seq, List<Document> docs) {

            List<List<Document>> toReturn = new List<List<Document>>();

            foreach (ClusterNode n in seq.Last().Recipients) {
                foreach (Document d in docs) {
                    if (d.Sender.ID == n.ID) {
                        if ((DateTime.Compare(seq.Last().Date, d.Date) < 0) && (DateTime.Compare(seq.Last().Date.AddDays(7.0), d.Date) > 0)) {
                            List<Document> newSeq = new List<Document>();
                            foreach (Document d2 in seq) {
                                newSeq.Add(d2);
                            }
                            newSeq.Add(d);

                            List<Document> docs2 = new List<Document>();
                            foreach (Document d2 in docs) {
                                if (d2.Date > seq.Last().Date) {
                                    docs2.Add(d2);
                                }
                            }

                            List<List<Document>> newSequences = new List<List<Document>>();
                            newSequences = continueSequence(newSeq, docs2);

                            if (newSequences == null || newSequences.Count == 0) {
                                toReturn.Add(newSeq);
                            } else {
                                foreach (List<Document> sequence in newSequences) {
                                    toReturn.Add(sequence);
                                }
                            }
                        }
                    }
                }
            }

            if (toReturn.Count > 0) {
                return toReturn;
            } else {
                return null;
            }
        }

        public string Name {
            get { return "Message Clustering Algorithm"; }
        }

        public void Register() {
            NetworkModel.Instance.MessageProcessed += OnMessageProcessed;
            NetworkModel.Instance.TapeEnded += OnTapeEnded;
            NetworkModel.Instance.TapePaused += OnTapeEnded;
        }

        public void DeRegister() {
            throw new NotImplementedException();
        }
    }
}
